pub use crate::reader::Reader;
use crate::Config;

#[derive(Debug)]
pub enum Value {
	Void,
	Bool,
	Byte,
	Char,
	Short,
	Int,
	Long,
	Float,
	Double,
	Object(String),
	Array(Box<Value>),
}
impl Value {
	pub fn from_str(src: &str) -> Option<Self> {
		Self::parse(&mut Reader::new(&src))
	}
	pub fn parse<'a>(r: &mut Reader<'a>) -> Option<Self> {
		let next = r.next()?;
		let ret = match next {
			'V' => Self::Void,
			'Z' => Self::Bool,
			'B' => Self::Byte,
			'C' => Self::Char,
			'S' => Self::Short,
			'I' => Self::Int,
			'J' => Self::Long,
			'F' => Self::Float,
			'D' => Self::Double,
			'L' => {
				let s = r.capture(|r| r.consume_until_char(';'))?;
				r.consume_char(';');
				Self::Object(s.to_string())
			}
			'[' => Self::Array(Box::new(Value::parse(r)?)),
			_ => return None,
		};
		Some(ret)
	}
	pub fn fmt_const_tag(&'_ self) -> FmtConstTag<'_> {
		FmtConstTag(self)
	}
	pub fn fmt_jnim_type<'a>(&'a self, in_ret: bool, cfg: &'a Config) -> FmtArgnType<'a> {
		FmtArgnType(self, in_ret, cfg)
	}
}

pub struct FmtConstTag<'a>(pub &'a Value);
impl<'a> std::fmt::Display for FmtConstTag<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self.0 {
			Value::Void => panic!("ValueOutputRustConstTag void"),
			Value::Bool => f.write_str("u8")?,
			Value::Byte => f.write_str("i8")?,
			Value::Char => f.write_str("u16")?,
			Value::Short => f.write_str("i16")?,
			Value::Int => f.write_str("i32")?,
			Value::Long => f.write_str("i64")?,
			Value::Float => f.write_str("f32")?,
			Value::Double => f.write_str("f64")?,
			Value::Object(v) => match v.as_str() {
				"java/lang/String" => f.write_str("&'static str")?,
				_ => {
					println!("{}", v);
					panic!("ValueOutputRustConstTag Object")
				}
			},
			Value::Array(_) => panic!("ValueOutputRustConstTag Array"),
		}
		Ok(())
	}
}

pub struct FmtArgnType<'a>(pub &'a Value, pub bool, pub &'a Config);
impl<'a> std::fmt::Display for FmtArgnType<'a> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		match self.0 {
			Value::Void => {
				if self.1 {
					f.write_str("()")?
				} else {
					panic!("ValueOutputRustArgNJObjectType ()")
				}
			}
			Value::Bool => f.write_str("JBool")?,
			Value::Byte => f.write_str("JByte")?,
			Value::Char => f.write_str("JChar")?,
			Value::Short => f.write_str("JShort")?,
			Value::Int => f.write_str("JInt")?,
			Value::Long => f.write_str("JLong")?,
			Value::Float => f.write_str("JFloat")?,
			Value::Double => f.write_str("JDouble")?,
			Value::Object(s) => f.write_fmt(format_args!("&'a {}", self.2.name(s).to_rust_real()))?,
			Value::Array(v) => {
				match &**v {
					Value::Void => panic!("ValueOutputRustArgNJObjectType Array ()"),
					Value::Bool => f.write_str("&'a JArrayPrim<JBool>")?,
					Value::Byte => f.write_str("&'a JArrayPrim<JByte>")?,
					Value::Char => f.write_str("&'a JArrayPrim<JChar>")?,
					Value::Short => f.write_str("&'a JArrayPrim<JShort>")?,
					Value::Int => f.write_str("&'a JArrayPrim<JInt>")?,
					Value::Long => f.write_str("&'a JArrayPrim<JLong>")?,
					Value::Float => f.write_str("&'a JArrayPrim<JFloat>")?,
					Value::Double => f.write_str("&'a JArrayPrim<JDouble>")?,
					Value::Object(s) => f.write_fmt(format_args!("&'a JArrayObject<{}>", self.2.name(s).to_rust_real()))?,
					Value::Array(_) => f.write_str("&'a JArrayObject<JArray>")?,
				}
			}
		}
		Ok(())
	}
}
